#pragma once
#include<functional>
#include "Socket.hpp"
#include "TcpServer.hpp"
#include <memory>
#include <iostream>
#include <string>
#include <unordered_map>
#include <cstring>
#include"Log.hpp"
#include "Util.hpp"
#include<sstream>
using namespace SocketModule;
using namespace LogModule;
const std::string gspace = " ";        // 空格
const std::string glinespace = "\r\n"; // 换行符
const std::string glinesep = ": ";

const std::string webroot = "./wwwroot";   // web根目录
const std::string homepage = "index.html"; // 默认首页
const std::string page_404="/404.html";


class HttpRequest                           // 这个类是用来表示http请求的
{
public:
    HttpRequest():_is_interact(false)//初始化为false，静态网页不需要交互操作
    {
    }

    // 将结构化的数据转换为字符串的功能,就是序列化操作
    std::string Serialize()
    {
        return std::string();
    }

    void ParseReqLine(std::string &reqline)//对第一行请求行字符串格式内容进行内容的拆分
    {
        //将一个字符串拆成三个子串、
        //Get  / Http/ 1.1   这个就是reqline的格式了
        std::stringstream ss(reqline);//使用reqline进行ss的初始化操作
        ss>> _method>> _uri>> _version; // 按照空格进行分割，得到三个子串
        //解析完毕，三个参数就被设置好了
    }

    
    //实现，今天我们认为，reqstr就是一个完整的http请求报文，我们需要将它进行反序列化操作
    bool Deserialize(std::string &reqstr) // 反序列化操作,字符串转换为结构化数据
    {
        //1、提取请求行
        std::string reqline;//请求行
        bool res=Util::ReadOneLine(reqstr,&reqline,glinespace);
        //到这里我们就成功提取出来了一行的内容了
        LOG(LogLevel::DEBUG)<<"reqline:"<<reqline;

        //2、对请求行进行反序列化
        ParseReqLine(reqline);// 将提取内容转换为三块内容
        if(_uri=="/")
            _uri=webroot+_uri+homepage;//直接就是默认首页
        else
            //指定路径下的文件，而不是默认首页
            _uri=webroot+_uri;//这个uri就是我们要访问的资源路径，我们自己拼接一下web根目录
        
        // if(_method=="POST")//如果是POST请求，那么我们就需要提取请求体
        // {
        //     _is_interact=true;//交互状态的
        //     while(true)
        //     {
        //         Util::ReadOneLine(reqstr,&reqline,glinespace);// 继续提取请求行
        //         if(reqline!=glinespace)
        //         {
        //             //获得了request   header->key   value
        //         }
        //         else
        //         {
        //             break;
        //         }
        //     }
        //    //Util::ReadOneLine(reqstr,&reqline,glinespace);//正文的数据

        // }
        LOG(LogLevel::DEBUG)<<"Method:"<<_method;
        LOG(LogLevel::DEBUG)<<"URI:"<<_uri;
        LOG(LogLevel::DEBUG)<<"_version:"<<_version;
        const std::string temp="?";
        auto pos=_uri.find(temp);//查找"?"的位置
        if(pos!=std::string::npos)//如果找到了"?"的位置，说明有参数
        {
            return true;
        }
        _args=_uri.substr(pos+temp.size());//从pos+1开始截取到结尾，就是参数
        _uri=_uri.substr(0,pos);//将uri截取到"?"之前的位置
        _is_interact=true;
        return true;
    }

    std::string Uri(){return _uri;}
    bool IsInteract() {return _is_interact;}//判断是否交互的函数
    std::string Args(){return _args;}//将请求的参数进行返回操作
    ~HttpRequest()
    {
    }

private:
    std::string _method;  // HTTP请求方法
    std::string _uri;     // 请求的资源路径
    std::string _version; // HTTP协议版本

    std::unordered_map<std::string, std::string> _headers; // 请求头是key value组成的
    std::string _blankline;                                // 空行
    std::string _text;                                     // 请求体

    std::string _args; // 请求参数
    bool _is_interact;//是否交互
};

class HttpResponse
{
public:
    HttpResponse() : _blankline(glinespace) // 初始化空行
    ,_version("HTTP/1.1")
    {
    }
    // 将结构化的数据转换为字符串的功能,就是序列化操作
    // 实现：成熟的http，应答做序列化，不需要依赖第三方库，自己实现即可
    std::string Serialize()
    {
        // 版本号+空格+状态码（转换为字符串）+空格+状态码描述+换行符   这个就是状态行了
        std::string status_line = _version + gspace + std::to_string(_code) + gspace + _desc + glinespace; // 状态行

        // 响应报头
        std::string resp_header;
        for (auto &header : _headers) // 遍历这个header的map，将每个响应报头都取出来
        {
            // key:[空格]value
            std::string line = header.first + glinesep + header.second + glinespace; // 每一行的响应报头
            resp_header += line;                                                     // 将每一行的响应报头都拼接起来
        }

        // 返回状态行+响应报头+空行+响应体
        return status_line + resp_header + _blankline + _text; // 将状态行+响应报头+空行+响应体拼接起来返回
    }

    void SetTargetFile(const std::string &target) // 设置目标文件
    {
        _targetfile = target; // 设置目标文件,目标文件等于我们的传入的文件名
    }
    void SetCode(int code)
    {
        _code=code;
        switch(_code)
        {
            case 200:
                _desc="OK"; 
                break;
            case 301://永久重定向
                _desc="Moved Permanently";
                break;
            case 302://临时重定向
                _desc="Found";
                break;
            case 404:
                _desc="Not Found";
                break;
            default:
                break;
        }
    }
    void SetHeader(const std::string &key, const std::string &value) // 设置响应头
    {
        auto iter=_headers.find(key);//我们在报头中查到我们对应的值
        if(iter!=_headers.end())//如果找到了这个key，那么我们就不进行添加操作了
            return;
        _headers.insert(std::make_pair(key, value)); // 否则我们就添加一个新的key-value对到报头中
    }
    std::string Uri2Suffix(std::string &targetfile)//文件文件名后缀提取出来
    {
        //./wwwroot/index.html  这个就是我们要访问的uri 了，我们需要将这个rui中的文件后缀名提取出来
        auto pos =targetfile.rfind(".");//从targetfile倒着开始找，找到最后一个"."的位置
        if(pos==std::string::npos)//没找到这个"."的位置，说明这个文件没有后缀名
            return "text/html";
        
        std::string suffix=targetfile.substr(pos);//从pos开始截取到结尾，就是文件后缀名

        //截取到了后缀，我们进行一个抓换操作
        if (suffix==".html"|suffix==".htm")
            return "text/html";
        else if(suffix==".jpg")
            return "image/jpeg";
        else if(suffix==".png")
            return "image/png";
        else 
            return "";
    }
    bool MakeResponse()
    {
        if(_targetfile=="./wwwroot/favicon.ico")//如果我们当前的请求文件是这个图标文件的话，我们就过滤掉
        {
            LOG(LogLevel::DEBUG)<<"用户请求"<<_targetfile<<"忽略";
            return false;
        }

        if(_targetfile=="./wwwroot/redir_test")//当我们请求的路径是这个文件的时候，我们就进行我们的重定向操作
        {
            SetCode(301);//进行一个临时重定向操作，先把状态码设置为302
            SetHeader("Location","https://www.qq.com/");//添加响应头
            return true;
        }
        int filesize = Util::FileSize(_targetfile); // 获取目标文件大小
        bool res = Util::ReadFileContent(_targetfile, &_text);//将读取的内容放到我们的_text中
        if(!res)//读取失败的情况，就是给定的uri文件不存在的情况下，我们就启动报错界面
        {
            // _text="";
            // LOG(LogLevel::WARING)<<"404 Not Found";
            // SetCode(404);//设置状态码为404
            // _targetfile=webroot+page_404;//设置404页面
            // filesize = Util::FileSize(_targetfile); // 获取目标文件大小
            // //添加报头

            // Util::ReadFileContent(_targetfile, &_text);//读取404页面的内容到_text中
            // SetHeader("Content-Length",std::to_string(filesize));//设置响应头
           
           
            //文件打开失败了，我们直接临时重定向到404界面
            SetCode(302);//设置状态码为404
            SetHeader("Location","http://193.112.169.40:8080/404.html") ;//设置重定向的地址
            return true;
        }
        else//读取成功了
        {
            SetCode(200);//设置状态码为200
            filesize = Util::FileSize(_targetfile); // 获取目标文件大小
            std::string suffix=Uri2Suffix(_targetfile);//获取文件后缀名,将这个uri里面的文件名后缀提取出来
            SetHeader("Content-Length",std::to_string(filesize));//设置响应头
            SetHeader("Content-Type",suffix); // 设置响应头,设置下文件属性,根据我们传入的文件后缀名进行Content的设置操作
        
            SetHeader("Set-Cookie","username=zhangsan;passwd=123456");//进行cookie的设置
        }
        return true;
    }

    void SetText(const std::string &t)
    {
        _text = t; // 设置响应体,这个t就是客户传到客户端中的数据
    }

    // 返回给客户的信息我们这里主要针对于反序列化操作
    bool Deserialize(std::string &requstr) // 反序列化操作,字符串转换为结构化数据
    {
        return true;
    }
    ~HttpResponse()
    {
    }

public:
    // private:
    std::string _version; // HTTP协议版本
    int _code;            // HTTP状态码
    std::string _desc;    // HTTP状态描述---状态码如果出错了的话，返回的错误信息

    std::unordered_map<std::string, std::string> _headers; // 响应头是key value组成的
    
    std::vector<std::string>cookie;//单独来存放cookie，因为上面的unordered_map可能因为cookie的键相同，所以不能找到正确的值，所以不适合存放cookie
    
    std::string _blankline;                                // 空行
    std::string _text;                                     // 响应体

    //其他属性
    std::string _targetfile;//目标文件资源
};
//HttpRequest &req：一个指向 HttpRequest 对象的引用,通常包含 URL、请求头、请求体等
//HttpResponse &resp：一个指向 HttpResponse 对象的引用,如状态码、响应头、响应体等
using http_func_t =std::function<void(HttpRequest &req,HttpResponse&resp)>; // 定义一个http处理函数的类型


class Http
{
public:
    Http(uint16_t port)
        : tsvrp(std::make_unique<TcpServer>(port)) // 传入端口号进行tcp对象的初始化操作
    {
    }
    // 处理传过来的http请求
    void HandlerHttpRequest(std::shared_ptr<Socket> &sock, InetAddr &client)
    {
        std::string httpreqstr; // 定义一个缓冲区，用于接收http请求
        // 将客户端发过来的消息进行接收操作
        // 我们这里的recv是有问题的，因为tcp是面向字节流的
        // 所以当我们在读取请求的时候，我们不能保证我们读到的是一个完整的请求
        int n=sock->Recv(&httpreqstr); // 将接收到的数据存储在缓冲区中
        //这里我们不一定得到完整的报文，但是我们可以读到完整的报头
        //只要我们读到了空行就可以得到完整的报头
        //只要我们读到了空行，那么空行以前就是对应一个报文的完整报头
        //对报头进行反序列化，提取一个属性：Content-Length：有效载荷的长度
        //在从剩余的字符串内容中，提取content-length的值，然后根据这个值，我们就可以知道剩余的字符串中有多少有效载荷

        //我们这里缺少了一个对报文完整性的审核
        if(n>0)//读到了完整的报文
        {
            //所以今天我们就不再担心用户访问一个服务器上不存在的资源了，
            //我们更加不担心，给用户返回任何网页资源（html、css、js等）

            HttpRequest req; // 构建一个http请求对象
            HttpResponse resp; // 构建一个http应答对象
            req.Deserialize(httpreqstr); // 将接收到的字符串进行反序列化操作，得到一个http结构体对象，将对应的变量都进行初始化好
            //经过了上面的反序列化，我们就将uri进行初始化好了，以及其他的变量都初始化好了
            
            //  _uri:./wwwroot/login
            if(req.IsInteract())//交互的情况
            {
                if(_route.find(req.Uri())==_route.end())//在我们的路由表中找不到这个uri对应的处理函数
                {
                    //SetCode(404);//设置状态码为404，我们直接重定向为404页面
                }
                else//存在对应的处理函数
                {
                    _route[req.Uri()](req,resp);//调用对应的处理函数进行处理,_route[req.Uri()]是对应的函数，后面的传入的是req和resp，是处理函数的参数
                    std::string response_str = resp.Serialize(); // 将这个结构化数据进行序列化操作
                    sock->Send(response_str);                    // 将序列化后的字符串发送给客户端
                }
            }
            else//不交互的话说明就访问的是静态资源的
            {
                resp.SetTargetFile(req.Uri()); // 设置目标文件，将我们需要请求的uri传过去，这个其实就是目标目录
                if(resp.MakeResponse()==true) // 调用MakeResponse函数，对uri进行处理，看下给到的uri是否是有效的
                {
                
                    //只有我们的makeResponse函数返回true的时候，才会进行序列化操作，将结构化数据转换为字符串

                    //将结构体化的数据进行序列化操作，得到一个字符串
                    std::string response_str = resp.Serialize(); // 将这个结构化数据进行序列化操作
                    //获取我们的uri
                    //std::string filename =req.Uri(); //请求的资源路径   ./wwwroot/index.html  这个就是我们要访问的变量了
                    


                    // 直接构建http应答，内存级别+固定
                    // HttpResponse resp;          // 构建一个http请求对象
                    // resp._version = "HTTP/1.1"; // 版本号
                    // resp._code = 200;           // 状态码---success
                    // resp._desc = "OK";          // 状态描述

                    // //构建响应头

                    // //将web根目录带上去
                    // LOG(LogLevel::DEBUG)<<"用户请求:"<<filename;
                    // //将这个资源读取出来，写到正文_text中
                    // bool res = Util::ReadFileContent(filename, &(resp._text)); // 读取文件的内容传递到正文部分
                    // //如果这个文件不存在的话我们应该如何进行处理呢？我们要进行异常处理，
                    // (void)res;
                    sock->Send(response_str);                    // 将序列化后的字符串发送给客户端
                }
            }
        }
// #ifndef DEBUG
// #define DEBUG
#ifdef DEBUG
        // 收到请求
        std::string httpreqstr; // 定义一个缓冲区，用于接收http请求
        // 将客户端发过来的消息进行接收操作
        // 我们这里的recv是有问题的，因为tcp是面向字节流的
        // 所以当我们在读取请求的时候，我们不能保证我们读到的是一个完整的请求
        sock->Recv(&httpreqstr); // 将接收到的数据存储在缓冲区中
        std::cout << httpreqstr << std::endl
                  << std::flush;
        ;
        // 浏览器给我发过来的是一个大的http字符串

        // 直接构建http应答，内存级别+固定
        HttpResponse resp;          // 构建一个http请求对象
        resp._version = "HTTP/1.1"; // 版本号
        resp._code = 200;           // 状态码---success
        resp._desc = "OK";          // 状态描述

        // resp._text="<!DOCTYPE html>\
        //                 <html lang=\"zh-CN\">\
        //                 <head>\
        //                     <meta charset=\"UTF-8\">\
        //                     <title>我的第一个网页</title>\
        //                 </head>\
        //                 <body>\
        //                     <h1>欢迎来到我的网页</h1>\
        //                     <p>这是一段简单的文本内容，展示 HTML 基础结构。</p>\
        //                 </body>\
        //                 </html>";//响应体
        // 构建响应头
        std::string filename = webroot + homepage;                 // web根目录+默认首页   ./wwwroot/index.html  这个就是我们要访问的变量了
        bool res = Util::ReadFileContent(filename, &(resp._text)); // 读取文件的内容传递到正文部分
        (void)res;

        std::string response_str = resp.Serialize(); // 将这个结构化数据进行序列化操作
        sock->Send(response_str);                    // 将序列化后的字符串发送给客户端

#endif
    }
    void Start()
    {
        // 当我们启动的时候，底层只要出现了新链接的话，那么代码就会自动进行回调操作，执行我们上层对应的HandlerHttpRequest函数
        tsvrp->Start([this](std::shared_ptr<Socket> &sock, InetAddr &client)
                     {
                         this->HandlerHttpRequest(sock, client); // 将这个链接进行处理
                     });
    }
    void RegisterService(const std::string name, http_func_t h)//注册一个 HTTP 服务处理函数，方便后续根据不同的 name 找到对应的处理逻辑。
    {
        auto iter=_route.find(name);
        if(iter==_route.end())//找不到
        {
            _route.insert(std::make_pair(name, h)); // 插入一个新的处理函数，将这个name和h注册进来
        }
    }
    ~Http()
    {
    }

private:
    // http服务器的底层是tcp,所以我们是需要先进行tcp对象的构建的
    std::unique_ptr<TcpServer> tsvrp; // 定义一个tcpserver的智能指针
    std::unordered_map<std::string, http_func_t> _route; // 定义一个http处理函数的map
};